iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
自我挑戰組

Unit Test 學習路系列 第 16

Day 15: React Testing Library - getBy** (一)

  • 分享至 

  • xImage
  •  

RTL Queries getBy** 系列 分別會有兩天時間來練習,今天練習:

  • getByRole
  • getByLabelText
  • getByPlaceholderText
  • getByText

getByRole

  • 這裡的「Role」,指的是 ARIA
    為了特殊人士需求,將 HTML 語意做加強補充的功能,也是「無障礙網頁」需求之一。
  • HTML Role 列表:https://www.w3.org/TR/html-aria/#docconformance
  • 基本常見的 HTML Element 預設的role:
    • <button></button> 相當於 role="button"
    • <form></form> 相當於 role="form"
    • <a href=""></a> 相當於 role="link"
    • <input type="text" /> 相當於 role="textbox"
  • 或者,我們為 HTML Element 加入客製化語意,像是:<div role="button"></div>

我們透過這些特定屬性,作為測試的指定項目。

  • 舉例:我需要在畫面上顯示 Email 輸入框、下拉選單 以及送出按鈕。
export default function Applications(){

    return(
        <form action="">
            // Email 輸入框
            <div>
                <label htmlFor="email">Email</label>
                <input type="email" name="email" id="email" />
            </div>
            // 下拉選單
            <div>
                <label htmlFor="level"></label>
                <select name="" id="level">
                    <option value="High">High</option>
                    <option value="Medium">Medium</option>
                    <option value="Low">Low</option>
                </select>
            </div>
            // 送出按鈕
            <button type="submit">Submit</button>
        </form>
    )
}

我要測試上面三個 HTML Element 有沒有顯示在畫面上:

import { render, screen } from "@testing-library/react";
import Applications from "./application";

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const emailEl = screen.getByRole("textbox");
        expect(emailEl).toBeInTheDocument();

        const selectionEl = screen.getByRole("combobox");
        expect(selectionEl).toBeInTheDocument();

        const buttonEl = screen.getByRole("button");
        expect(buttonEl).toBeInTheDocument();
    })
});

測試結果: PASS src/components/application/application.test.tsx

補充說明:select 元素 role="combobox" 代表一組合框,具有可收縮的選項列表的元件,使用者可以選擇其中一個選項。


當使用 getByRole,出現多個 Element 符合的情況

我們可能會遇到一個情況是:一個測試 Component 包含多個符合條件的 role 的場景。

一個 Component 有多個 input 的時候

export default function Applications(){

    return(
        <form action="">
            <div>
                <label htmlFor="email">Email</label>
                <input type="email" name="email" id="email" />
            </div>
            <div>
                <label htmlFor="name">Name</label>
                <input type="text" name="name" id="name" />
            </div>
           ...
        </form>
    )
}

當我們使用 getByRole("textbox") 驗證 input 測試時,

import { render, screen } from "@testing-library/react";
import Applications from "./application";

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const emailEl = screen.getByRole("textbox");
        expect(emailEl).toBeInTheDocument();

        const nameEl = screen.getByRole("textbox");
        expect(nameEl).toBeInTheDocument();
        
        ...
    })
});

測試結果為:
FAIL src/components/application/application.test.tsx
● Application › Render correctly
TestingLibraryElementError: Found multiple elements with the role "textbox"

Application Component 內有多個符合 role="textbox" 的選項,無法確認我想測試的是哪一個 Element。

這個時候,我們可以使用 getByRole() 的第二個參數選項,帶入明確的指定元素。

import { render, screen } from "@testing-library/react";
import Applications from "./application";

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const emailEl = screen.getByRole("textbox", {
            name: "Email" // 指定該 input 的 label value
        });
        expect(emailEl).toBeInTheDocument();

        const nameEl = screen.getByRole("textbox", {
            name: "Name" // 指定該 input 的 label value
        });
        expect(nameEl).toBeInTheDocument();
        ...
    })
});

測試結果:
PASS src/components/application/application.test.tsx
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total

一個 Component 有多個 heading 的時候

export default function Applications(){
    return(
        <>
           <h1>Application</h1>
           <h2>User Info</h2>
           ...
        </>
    )
}

測試寫法:我們可以用 text value 去指定到我們想測試的那個 Element

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const h1El = screen.getByRole("heading", {
            name: "Application"
        });
        expect(h1El).toBeInTheDocument();

        const h2El = screen.getByRole("heading", {
            name: "User Info"
        });
        expect(h2El).toBeInTheDocument();
        ...
    })
});

或者,指定 heading level

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const h1El = screen.getByRole("heading", {
            level: 1
        });
        expect(h1El).toBeInTheDocument();

        const h2El = screen.getByRole("heading", {
            level: 2
        });
        expect(h2El).toBeInTheDocument();
        ...
    })
});

測試結果:
PASS src/components/application/application.test.tsx
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total


getByLabelText

  • 故名思義是取得 label 的 text value。
  • 舉例:
export default function Applications(){
    return(
        <form action="">
            // Label 寫法一
            <div>
                <label htmlFor="email">Email</label>
                <input type="email" name="email" id="email" />
            </div>
            
            // Label 寫法二
            <div>
                <label htmlFor="name">
                    Name<input type="text" name="name" id="name" />
                </label>
            </div>
            ...
        </form>
    )
}

撰寫測試:

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const emailEl = screen.getByLabelText("Email");
        expect(emailEl).toBeInTheDocument();

        const nameEl = screen.getByLabelText("Name");
        expect(nameEl).toBeInTheDocument();
    })
});

測試結果:

PASS src/components/application/application.test.tsx
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total


當使用 getByLabelText,出現多個 Element 符合的情況

export default function Applications(){
    return(
        <form action="">
            <div>
                <label htmlFor="name">
                    Name<input type="text" name="name" id="name" />
                </label>
            </div>
            <div>
                <label htmlFor="level">Name</label>
                <select name="" id="level">
                    <option value="High">High</option>
                    <option value="Medium">Medium</option>
                    <option value="Low">Low</option>
                </select>
            </div>
            ...
        </form>
    )
}

撰寫測試:

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const nameEl = screen.getByLabelText("Name");
        expect(nameEl).toBeInTheDocument();
    })
});

測試結果:
FAIL src/components/application/application.test.tsx
● Application › Render correctly
TestingLibraryElementError: Found multiple elements with the text of: Name

這時候,我們依樣可以使用 getByLabelText 第二個參數來明確指定要進行測試的 Element

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);
        
        const nameEl = screen.getByLabelText("Name", {
            selector: "input"
        });
        expect(nameEl).toBeInTheDocument();

        const levelNameEl = screen.getByLabelText("Name", {
            selector: "select"
        });
        expect(levelNameEl).toBeInTheDocument();
    })
});

測試結果:
PASS src/components/application/application.test.tsx
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total


getByPlaceholderText

  • 取得有使用 (1) placeholder 屬性 (2) 符合 指定的 placeholder text value
  • 舉例:
export default function Applications(){
    return(
        <form action="">
            <label htmlFor="email">Email</label>
            <input 
                type="email" 
                name="email" 
                id="email" 
                placeholder="Enter Your Email" 
            />
        </form>
    )
}

撰寫測試:

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const emailEl = screen.getByPlaceholderText("Enter Your Email");
        expect(emailEl).toBeInTheDocument();
    })
});

測試結果:
PASS src/components/application/application.test.tsx
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total


getByText

  • 使用 getByText 取的 DOM text value。
  • 舉例:
export default function Applications(){
    return(
        <>
            <h1>Application</h1>
            <h2>User Info</h2>
            <p>Note: Required Fields</p>
        </>
    )
}

撰寫測試:

describe("Application", () => {
    test("Render correctly", () => {
        render(<Applications />);

        const paragraphEl = screen.getByText("Note: Required Fields");
        expect(paragraphEl).toBeInTheDocument();
    })
});

測試結果:
PASS src/components/application/application.test.tsx
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total


參考資源


上一篇
Day14: 關於 React Testing Library 測試
下一篇
Day 16: React Testing Library - getBy** (二)
系列文
Unit Test 學習路31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言